package contract

import (
	"bytes"
	"context"
	"errors"
	"math/big"
	"os"
	"raymond/global"
	"strings"

	"github.com/ethereum/go-ethereum/accounts/abi"
	"github.com/ethereum/go-ethereum/accounts/abi/bind"
	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/core/types"
)

func BuildContract(address string, name string) (*bind.BoundContract, error) {
	client := global.Client()
	contractAbi, _, err := _loadContract(name)
	if err != nil {
		return nil, err
	}
	addr := common.HexToAddress(address)
	return bind.NewBoundContract(addr, *contractAbi, client, client, client), nil
}

func BuildContractOnlyABIFile(address string, name string) (*bind.BoundContract, error) {
	client := global.Client()
	contractAbi, err := _loadABIFile(name)
	if err != nil {
		return nil, err
	}
	addr := common.HexToAddress(address)
	return bind.NewBoundContract(addr, *contractAbi, client, client, client), nil
}

func BuildContractOnlyABI(address string, content string) (*bind.BoundContract, error) {
	client := global.Client()
	contractAbi, err := _loadABI(content)
	if err != nil {
		return nil, err
	}
	addr := common.HexToAddress(address)
	return bind.NewBoundContract(addr, *contractAbi, client, client, client), nil
}

func _loadContract(name string) (*abi.ABI, []byte, error) {
	abiBytes, err := os.ReadFile("../contract/" + name + ".abi")
	if err != nil {
		return nil, nil, err
	}
	abi, err := abi.JSON(bytes.NewReader(abiBytes))
	if err != nil {
		return nil, nil, err
	}
	binBytes, err := os.ReadFile("../contract/" + name + ".bin")
	if err != nil {
		return nil, nil, err
	}
	return &abi, binBytes, nil
}

func _loadABIFile(name string) (*abi.ABI, error) {
	abiBytes, err := os.ReadFile("../contract/" + name + ".abi")
	if err != nil {
		return nil, err
	}
	abi, err := abi.JSON(bytes.NewReader(abiBytes))
	if err != nil {
		return nil, err
	}
	return &abi, nil
}

func _loadABI(content string) (*abi.ABI, error) {
	abi, err := abi.JSON(strings.NewReader(content))
	if err != nil {
		return nil, err
	}
	return &abi, nil
}

func _deployContract(auth *bind.TransactOpts, contractAbi *abi.ABI, contractBin []byte, params ...interface{}) (common.Address, *types.Transaction, *bind.BoundContract, error) {
	client := global.Client()
	bytecode := string(contractBin)
	var tx *types.Transaction
	var contract *bind.BoundContract
	var err error
	var address common.Address
	var receipt *types.Receipt
	if params == nil || (len(params) == 1 && params[0] == nil) {
		_, tx, contract, err = bind.DeployContract(auth, *contractAbi, common.FromHex(bytecode), client)
	} else {
		_, tx, contract, err = bind.DeployContract(auth, *contractAbi, common.FromHex(bytecode), client, params...)
	}
	if err != nil {
		return common.Address{}, nil, nil, err
	}
	address, err = bind.WaitDeployed(context.Background(), client, tx)
	if err != nil {
		return common.Address{}, nil, nil, err
	}
	receipt, err = client.TransactionReceipt(context.Background(), tx.Hash())
	if err != nil {
		return common.Address{}, nil, nil, err
	}
	status := receipt.Status
	if status != 1 {
		return common.Address{}, nil, nil, errors.New("Deploy deployment failed for contract:" + address.Hex())
	}

	return address, tx, contract, nil
}

// 第一个账号是coinbase,不用这个部署
func DeployByKeyStore(name string, senderIdx uint, params ...interface{}) (common.Address, *types.Transaction, *bind.BoundContract, error) {
	client := global.Client()
	auth, err := NewAuthByKeystore(senderIdx)
	if err != nil {
		return common.Address{}, nil, nil, err
	}
	gasPrice, err := client.SuggestGasPrice(context.Background())
	if err != nil {
		return common.Address{}, nil, nil, err
	}
	auth.GasPrice = gasPrice
	auth.GasLimit = 3000000
	contractAbi, contractBin, err := _loadContract(name)
	if err != nil {
		return common.Address{}, nil, nil, err
	}

	return _deployContract(auth, contractAbi, contractBin, params...)
}

func DeployByKey(name string, pr string, params ...interface{}) (common.Address, *types.Transaction, *bind.BoundContract, error) {
	auth, err := NewAuthByPrivatekey(pr)
	if err != nil {
		return common.Address{}, nil, nil, err
	}
	auth.GasPrice = big.NewInt(500000000)
	auth.GasLimit = 100000
	contractAbi, contractBin, err := _loadContract(name)
	if err != nil {
		return common.Address{}, nil, nil, err
	}

	return _deployContract(auth, contractAbi, contractBin, params...)
}

/*
调用由abigen生成的代码
func DeployByGen(name string, keystorePath string, params ...interface{}) (common.Address, *types.Transaction, *Contract, error) {
	client := global.Client()
	ks := keystore.NewKeyStore(keystorePath, keystore.StandardScryptN, keystore.StandardScryptP)
	//am := accounts.NewManager(&accounts.Config{InsecureUnlockAllowed: false}, ks)
	var acct accounts.Account
	accts := ks.Accounts()
	if len(accts) == 0 {
		acct0, err := ks.NewAccount(passphrase)
		if err != nil {
			return common.Address{}, nil, nil, err
		}
		acct = acct0
	} else {
		acct = accts[1]
	}
	chainID, err := client.NetworkID(context.Background())
	if err != nil {
		return common.Address{}, nil, nil, err
	}
	// 对账户解锁授权其进行部署
	err = ks.Unlock(acct, passphrase)
	if err != nil {
		return common.Address{}, nil, nil, err
	}
	auth, err := bind.NewKeyStoreTransactorWithChainID(ks, acct, chainID)
	if err != nil {
		return common.Address{}, nil, nil, err
	}
	gasPrice, err := client.SuggestGasPrice(context.Background())
	if err != nil {
		return common.Address{}, nil, nil, err
	}
	auth.GasPrice = gasPrice
	auth.GasLimit = 10000000
	return DeployContract(auth, client)
}
*/
